package remote

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"gitee.com/d-fw/gofw-cache/api"
	"gitee.com/d-fw/gofw-cache/internal/cache"
	"gitee.com/d-fw/gofw-cache/internal/node"
	"gitee.com/d-fw/gofw-cache/pkg/consistenthash"
)

const (
	defaultBasePath = "/_cache"
	defaultSelfAddr = "127.0.0.1:5252"
)

type HttpPool struct {
	basePath    string // The prefix /_cache
	selfAddr    string // Gets the path of the node itself [ip:port]  host
	mu          sync.Mutex
	hashRing    *consistenthash.Map    // hashRing
	httpGetters map[string]*httpGetter // 127.0.0.1 -> 127.0.0.1/_cache/
}

func NewHttpPool(selfAddr string) *HttpPool {
	return &HttpPool{
		basePath: defaultBasePath,
		selfAddr: selfAddr,
	}
}

func NewDefaultHttpPool() *HttpPool {
	return NewHttpPool(defaultSelfAddr)
}

// Register the cluster in the hash ring
func (p *HttpPool) SetClusterAddr(addrs ...string) {
	if len(addrs) < 1 {
		panic("SetClusterAddr params error")
	}
	p.mu.Lock()
	defer p.mu.Unlock()
	// Register the cluster in the hash ring
	p.hashRing, _ = consistenthash.NewDefaultHashRing()
	p.hashRing.Set(addrs...)
	// Initializes the list of remote mappings
	p.httpGetters = make(map[string]*httpGetter)
	// eg.    127.0.0.1 -> 127.0.0.1/_cache/
	for _, addr := range addrs {
		p.httpGetters[addr] = &httpGetter{
			baseURL: addr + p.basePath,
		}
	}
}

// The key is passed through the hash ring to find the real host
func (p *HttpPool) PickNode(key string) (peer node.NodeGetter, ok bool) {
	p.mu.Lock()
	defer p.mu.Unlock()
	if node := p.hashRing.Get(key); node != "" && node != p.selfAddr {
		log.Printf("key:[%s] in the node:[%s]\n", key, node)
		return p.httpGetters[node], true
	}
	return nil, false
}

func (p *HttpPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.RequestURI() == "/favicon.ico" {
		return
	}
	if !strings.HasPrefix(r.URL.Path, p.basePath) {
		panic("HttpInfo serving unexpected path: " + r.URL.Path)
	}
	params := strings.Split(r.URL.Path, "/")
	if len(params) < 5 {
		panic("HttpInfo serving unexpected path: " + r.URL.Path)
	}
	log.Printf("Receive remote: %s %s\n", r.Method, r.URL.Path)
	w.Header().Set("Content-Type", "application/json")
	// /_cache/[wddcache]/100/[127.0.0.1:8000]/[127.0.0.1:8000,127.0.0.1:8001,127.0.0.1:8002]
	// /_cache/[fwcache]/100/-1/-1
	// create cache name or update cache capacity
	if string(params[2][0]) == "[" && string(params[2][len(params[2])-1]) == "]" {
		CreateCache(w, params)
		return
	}
	c := cache.GetCache(params[2])
	if c == nil {
		api.NoCache(w, params)
		return
	}
	if params[3] == "_del" && len(params) == 5 {
		// /basePath/cacheName/_del/key --- > After segmentation for [ , basePath, cacheName, _del, key]
		api.DelCache(w, c, params)
	} else if params[3] == "_get" && len(params) == 5 {
		// /basePath/cacheName/_get/key  --- > After segmentation for [ , basePath, cacheName, _get, key]
		api.GetCache(w, c, params)
	} else if params[3] == "_set" && len(params) <= 6 {
		// /basePath/cacheName/_set/key/value --- > After segmentation for [ , basePath, cacheName, _del, key, value]
		// /basePath/cacheName/_set/key/value/ttl --- > After segmentation for [ , basePath, cacheName, _del, key, value, ttl]
		api.SetCache(w, params, c)
	} else if params[3] == "_ping" {
		api.PingCache(w)
	}

}

// Create a new cache group and set cacheName, capacity, selfAddr, and clusterAddr
// If cacheName already exists, changing only the capacity, selfAddr and clusterAddr parameters will not work
func CreateCache(w http.ResponseWriter, params []string) {
	cacheName := strings.Trim(strings.Trim(params[2], "["), "]")
	capacity, _ := strconv.Atoi(params[3])
	selfAddr := strings.Trim(strings.Trim(params[4], "["), "]")
	clusterAddr := strings.Trim(strings.Trim(params[5], "["), "]")
	c := cache.NewCache(cacheName, capacity)
	httpPool := NewHttpPool(selfAddr)
	httpPool.SetClusterAddr(strings.Split(clusterAddr, ",")...)
	c.RegisterNodes(httpPool)
	result := new(api.Result)
	result.Code = 200
	bytes, _ := json.Marshal(result)
	_, _ = w.Write(bytes)
}
